package com.tools.common.security.id;

import com.tools.common.container.r.OnlyReadArrayList;
import com.tools.common.exception.InvalidOperationException;
import com.tools.common.object.Note;
import com.tools.common.security.SysKit;

import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 自定义数字 ID 生成器实例
 * */
@Note("自定义数字 ID 生成器实例")
public final class NumberID {

    @Note("实例锁，用于保证并发安全")
    private final Lock lock;

    @Note("本机 IPv4 地址的哈希值")
    private static Integer LOCAL_IPV4_HASH;

    @Note("本机 MAC 地址的哈希数组")
    private static int[] MAC_HASHCODE_ARRAY;

    @Note("如果用到自增，那就使用这个锁进行隔离")
    private Lock incrLock;

    @Note("上次自增过后的 ID")
    private long previousID;


    /* *******************************************************************************************
     *
     *          构造器
     *
     * *******************************************************************************************
     * */

    public NumberID() {
        private_lazyLoadMACHashArray();
        private_lazyLoadLocalIPv4Hash();
        this.lock = new ReentrantLock();
    }


    public NumberID(long sourceID) {
        this();
        this.enableIncrement(sourceID);
    }


    public static NumberID of() {
        return new NumberID();
    }

    public static NumberID of(long sourceID) {
        return new NumberID(sourceID);
    }


    /* *******************************************************************************************
     *
     *          功能方法
     *
     * *******************************************************************************************
     * */

    @Note("获取一个线程安全且不重复的 13 位以上的纯数字 ID")
    public long newID() {
        lock.lock();
        long id;
        try {
            ThreadLocalRandom localRandom = ThreadLocalRandom.current();
            long time = System.currentTimeMillis();
            long num1 = localRandom.nextLong(500000000000L);
            int num2 = localRandom.nextInt(Integer.MAX_VALUE);
            int index = localRandom.nextInt(MAC_HASHCODE_ARRAY.length);
            id = ++time - Math.abs(num1) - Math.abs(num2)
                    - LOCAL_IPV4_HASH
                    - MAC_HASHCODE_ARRAY[index];
        } finally {
            lock.unlock();
        }
        return ++id;
    }



    @Note("获取一个唯一的 13 位的纯数字 ID，并对数字顺序做混淆")
    public long newShuffleID() {
        char[] idChars = String.valueOf(this.newID()).toCharArray();
        int index;
        char temp;
        int len = idChars.length;
        for (int i = 0; i < len; i++) {
            index = ThreadLocalRandom.current().nextInt(len);
            if(i == index) continue;
            temp = idChars[i];
            idChars[i] = idChars[index];
            idChars[index] = temp;
        }
        return Long.parseLong(new String(idChars));
    }


    @Note("开启自增 ID 功能，sourceID 是需要开始自增的原始 ID。" +
            "以后每次调用 incrementID() 方法都会在该基础上进行自增。")
    public void enableIncrement(long sourceID) {
        if(incrLock != null) return;
        synchronized (this) {
            if(incrLock != null) return;
            this.incrLock = new ReentrantLock();
            this.previousID = sourceID;
        }
    }


    @Note("自增 ID 方法。在调用之前需要先调用 enableIncrement(long sourceID) 开启功能并设置原始 ID")
    public long incrementID() {
        if(incrLock == null) throw new InvalidOperationException("你需要先调用 enableIncrement(long sourceID) 方法开启自增功能并赋予最原始的 ID");
        incrLock.lock();
        try {
            long newID = previousID + 1;
            previousID = newID;
            return newID;
        } finally {
            incrLock.unlock();
        }
    }


    @Note("批量生成 number 个 13 位的数字 ID，整体按从小到大排序")
    public TreeSet<Long> newBatchID(int number) {
        if(number <= 0) return new TreeSet<>();
        TreeSet<Long> idSet = new TreeSet<>();
        for (int i = 0; i < number; i++) {
            idSet.add(this.newID());
        }
        return idSet;
    }


    @Note("批量生成 number 个 13 位的乱序数字 ID，整体按从小到大排序")
    public TreeSet<Long> newBatchShuffleId(int number) {
        if(number <= 0) return new TreeSet<>();
        TreeSet<Long> idSet = new TreeSet<>();
        for (int i = 0; i < number; i++) {
            idSet.add(this.newShuffleID());
        }
        return idSet;
    }


    @Note("批量生成 number 个 13 位的自增数字 ID，整体按从小到大排序")
    public TreeSet<Long> newBatchIncrId(int number) {
        if(number <= 0) return new TreeSet<>();
        if(incrLock == null) throw new InvalidOperationException("你需要先调用 enableIncrement(long sourceID) 方法开启自增功能并赋予最原始的 ID");
        incrLock.lock();
        try {
            TreeSet<Long> idSet = new TreeSet<>();
            for (int i = 0; i < number; i++) {
                ++previousID;
                idSet.add(previousID);
            }
            return idSet;
        } finally {
            incrLock.unlock();
        }
    }


    /* *******************************************************************************************
     *
     *          私有逻辑
     *
     * *******************************************************************************************
     * */


    @Note("懒加载本机 MAC 地址的哈希数组")
    private static void private_lazyLoadMACHashArray() {
        if(MAC_HASHCODE_ARRAY != null) return;
        synchronized (NumberID.class) {
            if(MAC_HASHCODE_ARRAY != null) return;
            OnlyReadArrayList<String> allMacAddress = SysKit.getLocalAllMac();
            int size = allMacAddress.size();
            MAC_HASHCODE_ARRAY = new int[size];
            for (int i = 0; i < size; i++) {
                MAC_HASHCODE_ARRAY[i] = allMacAddress.get(i).hashCode();
            }
        }
    }

    @Note("懒加载本机 IPv4 地址的哈希值")
    private static void private_lazyLoadLocalIPv4Hash() {
        if(LOCAL_IPV4_HASH != null) return;
        synchronized (NumberID.class) {
            if(LOCAL_IPV4_HASH != null) return;
            String ipv4 = SysKit.getLocalFirstIpv4();
            int hash = (ipv4 == null) ? UUID.randomUUID().toString().hashCode() : ipv4.hashCode();
            LOCAL_IPV4_HASH = Math.abs(hash);
        }
    }
}
